﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Dokan;

namespace MirrorDrive.Operations.Cache
{
    //TODO: Add cache entry life time
    internal class CacheSystem
    {
        private DirectoryEntry _root;

        public int MaxCacheEntries { get; private set; }
        public int CacheEntries { get; internal set; }

        public CacheSystem()
        {
            this.MaxCacheEntries = 10240;

            _root = new DirectoryEntry(null, this);
        }

        private void CheckCapacity()
        {
            if (CacheEntries > MaxCacheEntries)
                Clear();
        }

        public ICacheEntry Find(string filename)
        {
            lock (_root)
            {
                string[] directories = null;

                if (filename == "\\")
                    directories = new string[] { "" };
                else directories = filename.Split('\\');

                DirectoryEntry currentDirectory = _root;
                DirectoryEntry previousDirectory = null;
                for (int i = 0; i < directories.Length; i++)
                {
                    previousDirectory = currentDirectory;
                    currentDirectory = currentDirectory.GetDirectory(directories[i], false);

                    if (currentDirectory == null)
                    {
                        if (directories.Length - 1 == i && previousDirectory.HasFiles)
                            return previousDirectory.GetFile(directories[i], false);
                        else return null;
                    }
                   
                }
                return currentDirectory;
            }
        }
        public FileEntry CacheFile(string filename, bool autoCreate)
        {
            lock (_root)
            {
                CheckCapacity();

                string file = filename.Substring(filename.LastIndexOf("\\") + 1);

                DirectoryEntry dir = CacheDirectory(filename.Substring(0, filename.Length - file.Length -1), autoCreate);
                if (dir == null || (!autoCreate && !dir.HasFiles)) return null;

                return dir.GetFile(file, autoCreate);
            }
        }
        public DirectoryEntry CacheDirectory(string filename, bool autoCreate)
        {
            lock (_root)
            {
                CheckCapacity();

                string[] directories = null;

                if (filename == "\\")
                    directories = new string[] { "" };
                else directories = filename.Split('\\');
                   

                DirectoryEntry currentDirectory = _root;
                for (int i = 0; i < directories.Length; i++)
                {
                    currentDirectory = currentDirectory.GetDirectory(directories[i], autoCreate);

                    if (i +1 != directories.Length && (currentDirectory == null || (!autoCreate && !currentDirectory.HasDirectories))) return null;
                }

                return currentDirectory;
            }
        }
        public void Clear()
        {
            Logger.Info("Clear called!");
            _root.ResetFindFiles();
            CacheEntries = 0;
        }
    }

    internal interface ICacheEntry
    {
        FileInformation GetFileInformationValue { get; set; }

        ReturnCode GetFileInformation { get; }
        List<CreateFileReturn> CreateFile { get; }

        DirectoryEntry Parent { get; }

        void ResetGetFileInformation();
        void ResetCreateFile();
    }

    internal class DirectoryEntry : ICacheEntry
    {
        public ArrayList FindFilesValue { get; set; }
        public FileInformation GetFileInformationValue { get; set; }

        public ReturnCode GetFileInformation { get; private set; }
        public ReturnCode FindFiles { get; private set; }
        public ReturnCode OpenDirectory { get; private set; }
        public List<CreateFileReturn> CreateFile { get; private set; }

        public SortedList<string, FileEntry> Files { get; private set; }
        public SortedList<string, DirectoryEntry> Directories { get; private set; }

        public bool HasDirectories { get { lock (Directories) { return Directories.Count != 0; } } }
        public bool HasFiles { get { lock (Files) { return Files.Count != 0; } } }

        public DirectoryEntry Parent { get; private set; }

        private CacheSystem _cache;

        public DirectoryEntry(DirectoryEntry parent, CacheSystem cache)
        {
            _cache = cache;

            this.Parent = parent;

            this.FindFilesValue = null;
            this.GetFileInformationValue = null;

            this.GetFileInformation = new ReturnCode();
            this.FindFiles = new ReturnCode();
            this.OpenDirectory = new ReturnCode();
            this.CreateFile = new List<CreateFileReturn>();

            this.Files = new SortedList<string, FileEntry>();
            this.Directories = new SortedList<string, DirectoryEntry>();
        }

        public DirectoryEntry GetDirectory(string dirName, bool autoCreate)
        {
            lock (Directories)
            {
                if (Directories.ContainsKey(dirName))
                    return Directories[dirName];
                else
                {
                    if (autoCreate)
                    {
                        DirectoryEntry entry = new DirectoryEntry(this, _cache);
                        Directories.Add(dirName, entry);

                        _cache.CacheEntries++;
                        return entry;
                    }
                    return null;
                }
            }
        }
        public FileEntry GetFile(string fileName, bool autoCreate)
        {
            lock (Files)
            {
                if (Files.ContainsKey(fileName))
                    return Files[fileName];
                else
                {
                    if (autoCreate)
                    {
                        FileEntry entry = new FileEntry(this);
                        Files.Add(fileName, entry);

                        _cache.CacheEntries++;
                        return entry;
                    }
                    return null;
                }
            }
        }

        public void ResetGetFileInformation()
        {
            GetFileInformation.Reset();
            GetFileInformationValue = null;
        }
        public void ResetCreateFile()
        {
            CreateFile.Clear();
        }
        public void ResetFindFiles()
        {
            FindFiles.Reset();
            FindFilesValue = null;

            this.Directories.Clear();
            this.Files.Clear();
        }
        public void ResetOpenDirectory()
        {
            OpenDirectory.Reset();
        }

        public void ResetAll()
        {
            ResetCreateFile();
            ResetOpenDirectory();
            ResetFindFiles();
            ResetGetFileInformation();
        }
    }
    internal class FileEntry : ICacheEntry
    {
        public FileInformation GetFileInformationValue { get; set; }

        public ReturnCode GetFileInformation { get; private set; }
        public List<CreateFileReturn> CreateFile { get; private set; }

        public DirectoryEntry Parent { get; private set; }

        public FileEntry(DirectoryEntry parent)
        {
            this.Parent = parent;

            this.GetFileInformationValue = null;

            this.GetFileInformation = new ReturnCode();
            this.CreateFile = new List<CreateFileReturn>();
        }

        public void ResetGetFileInformation()
        {
            GetFileInformation.Reset();
            GetFileInformationValue = null;
        }
        public void ResetCreateFile()
        {
            CreateFile.Clear();
        }
        public void ResetAll()
        {
            ResetCreateFile();
            ResetGetFileInformation();
        }
    }

    internal class CreateFileReturn : ReturnCode
    {
        public FileMode Mode { get; set; }
        public FileAccess Access { get; set; }
        public FileShare Share { get; set; }
        public FileOptions Options { get; set; }

        public bool Compare(CreateFileReturn b)
        {
            return Mode == b.Mode && Access == b.Access && Share == b.Share && Options == b.Options;
        }
    }
    internal class ReturnCode
    {
        private int _returnCode = 0;
        public int ReturnValue { get { return _returnCode; } set { IsSet = true; _returnCode = value; } }
        
        public bool IsSet { get; private set; }

        public void Reset()
        {
            IsSet = false;
        }
    }
}
